﻿// See https://aka.ms/new-console-template for more information
using AnalysisEnvironment;
using CommandLine;
using DllCallAnalysis45;
using DllMethodCalledAnalysis45.Helpers;
using DllMethodCalledAnalysis45.Models;
using Newtonsoft.Json;
using SDILReader;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;

public partial class Program
{
    //public void Test222()
    //{
    //    Task.Factory.StartNew(() =>
    //    {
    //        Console.WriteLine("Hi");
    //    });
    //}


    public static void Main(string[] args)
    {
        Globals.LoadOpCodes();

        //var methods = typeof(Program).GetMethods();
        //var method = methods.FirstOrDefault(n => n.Name == "Test222");
        //var body = new MethodBodyReader(method).GetBodyCode();

        LogHelper.WriteLine("Hello, World!");

        Parser.Default.ParseArguments<UserInputArgs>(args).WithParsed<UserInputArgs>(input =>
        {
            if (File.Exists(input.ConfigFile) != true)
            {
                Console.WriteLine($"无法找到配置文件信息");
                return;
            }

            var config = JsonConvert.DeserializeObject<ProjectConfig>(File.ReadAllText(input.ConfigFile));

            var file = Path.GetFileName(config.FileName);

            var options = new ProjectPackageInitOptions
            {
                ProjectPath = config.ProjectPath,
                SearchPaths = config.SearchPaths?.ToList(),
                //NugetPaths = config.NugetPaths?.ToList(),
                //NugetUrls = config.NugetUrls?.ToList()
            };

            using (ProjectEnvironment.BuildEnvironment(options))
            {
                try
                {
                    AnalysisQuote(config);
                    //AnalysisTopQuote(config);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"处理文件 {file} 时出错了 {ExceptionHelper.BuildExceptionMessage(ex)}");
                }
            }
        });
    }

    private static void AnalysisTopQuote(ProjectConfig config)
    {
        var path = config.ProjectPath;
        var startFile = Path.GetFileName(config.FileName);
        var matchTexts = config.CheckNames;
        var methodFilter = config.MethodFilter;
        var assemblyFilter = config.AssemblyFilter;

        try
        {
            var suffixs = new List<string> { ".exe", ".dll" };

            var assemblies = Directory.GetFiles(path)
                .Where(n => suffixs.Any(m => n.EndsWith(m)))
                .Select(n => Assembly.Load(AssemblyName.GetAssemblyName(n)))
                .ToList();

            var allTypes = assemblies
                .SelectMany(t => { try { return t.GetTypes(); } catch { return new Type[0]; } })
                .ToArray();

            var methodCallMappings = MethodAnalysisHelper.GetAllMethodCallInfo(assemblies.ToArray(), AssemblyIsPassFilter, MethodInfoIsPassFilter);
            if (methodCallMappings?.Any() != true)
            {
                LogHelper.WriteLine($"无法找到任何有用的函数调用");
                return;
            }
            LogHelper.WriteLine($"成功找到 {methodCallMappings.Count} 调用信息");

            var methodCalledMappings = MethodAnalysisHelper.ReversalCallInfo(methodCallMappings);
            if (methodCalledMappings?.Any() != true)
            {
                LogHelper.WriteLine($"无法找到任何有用的被调用信息");
                return;
            }
            LogHelper.WriteLine($"成功找到 {methodCalledMappings.Count} 被调用信息");

            var matchMethods = methodCallMappings.Keys.Where(n => IsMatchCheckNames(n)).ToList();
            if (matchMethods?.Any() != true)
            {
                LogHelper.WriteLine($"无法根据关键字（{string.Join("、", matchTexts)}）找到任何需要被搜索的调用函数");
                return;
            }
            LogHelper.WriteLine($"成功根据关键字（{string.Join("、", matchTexts)}）找到{matchMethods.Count}条函数");

            LogHelper.WriteLine($"正在反向搜索顶级调用函数");

            var allTopCallMethods = new List<MethodInfo>();

            foreach (var method in matchMethods)
            {
                var topCallMethods = GetTopCallMethods(methodCalledMappings, method) ?? new List<MethodInfo>();

                LogHelper.WriteLine($"{method.DeclaringType.FullName}.{method.Name} 成功找到 {topCallMethods.Count} 个顶级引用");

                foreach (var itemCallMethod in topCallMethods)
                {
                    if (allTopCallMethods.Contains(itemCallMethod))
                        continue;

                    allTopCallMethods.Add(itemCallMethod);
                }
            }

            LogHelper.WriteLine($"成功找到 {allTopCallMethods.Count} 个顶级引用");

            foreach (var method in allTopCallMethods)
            {
                try
                {
                    LogHelper.WriteLine("==========================================");
                    LogHelper.WriteLine($"{method}");

                    var code = MethodAnalysisHelper.BuildCallTree(allTypes, method, MethodInfoIsPassFilter);

                    var findTexts = matchTexts.Where(n => code.Contains(n));

                    if (findTexts?.Any() == true)
                    {
                        LogHelper.WriteLine($"函数 {MethodAnalysisHelper.BuildMethodCallCode(method)} 包含 {string.Join("、", findTexts)} 调用");

                        var trimCode = MethodAnalysisHelper.TrimCallTree(code, matchTexts);
                        LogHelper.WriteLine(trimCode);
                    }
                    else
                    {
                        LogHelper.WriteLine($"函数 {MethodAnalysisHelper.BuildMethodCallCode(method)} 不包含{string.Join("、", matchTexts)}调用");
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.WriteLine($"处理函数{method.Name} 时出错：{ex.Message}");
                }

                LogHelper.WriteLine($"{nameof(AnalysisTopQuote)} Finsh");
            }
        }
        finally
        {
            LogHelper.Save($"{startFile}_{nameof(AnalysisTopQuote)}_{DateTime.Now.Ticks}.txt");
        }

        List<MethodInfo> GetTopCallMethods(Dictionary<MethodInfo, List<MethodInfo>> calledMapping, MethodInfo method)
        {
            var topCallMethods = new List<MethodInfo>();
            var touchMethods = new List<MethodInfo>();

            FullTopCallMethods(method);

            return topCallMethods;

            void FullTopCallMethods(MethodInfo current)
            {
                if (touchMethods.Contains(current))
                    return;

                touchMethods.Add(current);

                if (calledMapping.TryGetValue(current, out var calledList) != true || calledList?.Any() != true)
                {
                    if (current != method)
                        topCallMethods.Add(current);

                    return;
                }

                foreach (var item in calledList)
                {
                    FullTopCallMethods(item);
                }
            }
        }

        bool IsMatchCheckNames(MethodInfo method)
        {
            var code = MethodAnalysisHelper.BuildMethodCallCode(method);
            return matchTexts?.Any(n => code.Contains(n)) == true;
        }

        bool AssemblyIsPassFilter(Assembly assembly)
        {
            if (assembly == null)
                return false;

            if (assemblyFilter == null)
                return true;

            return assemblyFilter.IsPassFilter(assembly);
        }

        bool MethodInfoIsPassFilter(MethodInfo method)
        {
            if (method == null)
                return false;

            if (methodFilter == null)
                return true;

            return methodFilter.IsPassFilter(method);
        }
    }

    private static void AnalysisQuote(ProjectConfig config)
    {
        var path = config.ProjectPath;
        var startFile = Path.GetFileName(config.FileName);
        var matchTexts = config.CheckNames;
        var methodFilter = config.MethodFilter;
        var assemblyFilter = config.AssemblyFilter;
        //string path, string startFile, params string[] matchTexts;

        var startMethods = config.StartMethods;

        try
        {
            var suffixs = new List<string> { ".exe", ".dll" };

            var assembliesMappings = Directory.GetFiles(path)
                .Where(n => suffixs.Any(m => n.EndsWith(m)))
                .ToDictionary(n => Path.GetFileName(n), n => Assembly.Load(AssemblyName.GetAssemblyName(n)));

            var touchMethods = new List<MethodInfo>();

            var allStartTypes = assembliesMappings[startFile].GetTypes();

            var allTypes = assembliesMappings
                .Values
                .Where(n => AssemblyIsPassFilter(n))
                .SelectMany(t => { try { return t.GetTypes(); } catch { return new Type[0]; } })
                .ToArray();

            var methods = allStartTypes.SelectMany(n => n.GetMethods()).ToList();

            //var methodMapping = methods.GroupBy(n => MethodAnalysisHelper.BuildMethodName(n)).ToDictionary(n => n.Key, n => n.ToList());

            var mis = methods;

            foreach (var mi in mis)
            {
                if (IsStartMethod(mi) != true)
                    continue;

                try
                {
                    LogHelper.WriteLine("==========================================");
                    LogHelper.WriteLine($"{mi}");

                    var url = GetRouteUrl(mi);
                    if (string.IsNullOrEmpty(url) != true)
                        LogHelper.WriteLine(url);

                    var code = MethodAnalysisHelper.BuildCallTree(allTypes, mi, MethodInfoIsPassFilter);

                    var findTexts = matchTexts.Where(n => code.Contains(n));

                    if (findTexts?.Any() == true)
                    {
                        LogHelper.WriteLine($"函数 {MethodAnalysisHelper.BuildMethodCallCode(mi)} 包含 {string.Join("、", findTexts)} 调用");

                        var trimCode = MethodAnalysisHelper.TrimCallTree(code, matchTexts);
                        LogHelper.WriteLine(trimCode);
                    }
                    else
                    {
                        LogHelper.WriteLine($"函数 {MethodAnalysisHelper.BuildMethodCallCode(mi)} 不包含{string.Join("、", matchTexts)}调用");
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.WriteLine($"处理函数{mi.Name} 时出错：{ex.Message}");
                }
            }

            LogHelper.WriteLine($"{nameof(AnalysisQuote)} Finsh");
        }
        finally
        {
            var outputDir = Path.Combine($"Output", startFile);
            if (Directory.Exists(outputDir) != true)
                Directory.CreateDirectory(outputDir);

            LogHelper.Save(Path.Combine(outputDir, $"{DateTime.Now.ToString("yyyyMMddHHmmss")}.txt"));

            Process.Start(outputDir);
        }

        string GetRouteUrl(MethodInfo method)
        {
            var classAttributes = method.DeclaringType.GetCustomAttributes();
            var methodAttributes = method.GetCustomAttributes();

            var list = new List<string>();

            do
            {
                var routeAttribute = classAttributes.FirstOrDefault(n => n.GetType().Name == "RouteAttribute");
                if (routeAttribute != null)
                {
                    list.Add(GetPropertyValue(routeAttribute, "Template"));
                    break;
                }

                var routepreAttribute = classAttributes.FirstOrDefault(n => n.GetType().Name == "RoutePrefixAttribute");
                if (routepreAttribute != null)
                {
                    list.Add(GetPropertyValue(routepreAttribute, "Prefix"));
                    break;
                }

            } while (false);

            do
            {
                var routeAttribute = methodAttributes.FirstOrDefault(n => n.GetType().Name == "RouteAttribute");
                if (routeAttribute != null)
                {
                    list.Add(GetPropertyValue(routeAttribute, "Template"));
                    break;
                }
            } while (false);

            return string.Join("/", list);
        }

        string GetPropertyValue(object obj, string propertyName)
        {
            var property = obj.GetType().GetProperties()?.FirstOrDefault(n => n.Name == propertyName);
            if (property == null)
                return null;

            return property.GetValue(obj)?.ToString();
        }

        bool AssemblyIsPassFilter(Assembly assembly)
        {
            if (assembly == null)
                return false;

            if (assemblyFilter == null)
                return true;

            return assemblyFilter.IsPassFilter(assembly);
        }

        bool MethodInfoIsPassFilter(MethodInfo method)
        {
            if (method == null)
                return false;

            if (methodFilter == null)
                return true;

            return methodFilter.IsPassFilter(method);
        }

        bool IsStartMethod(MethodInfo methodInfo)
        {
            if (startMethods?.Any() != true)
                return true;

            var methodName = $"{methodInfo.ReturnType} {methodInfo.DeclaringType.FullName}::{methodInfo.Name}()";

            return startMethods.Any(n => methodName.Contains(n));
        }
    }
}

